﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml.Serialization;

namespace Love.Awesome.SimpleTiledImplementation
{

    public enum RenderOrderEnum
    {
        [XmlEnum("right-down")]
        RightDown,

        [XmlEnum("right-up")]
        RightUp,

        [XmlEnum("left-down")]
        LeftDown,

        [XmlEnum("left-up")]
        LeftUp
    }

    public enum staggerindexEnum
    {
        [XmlEnum("odd")]
        Odd,

        [XmlEnum("even")]
        Even
    }

    public enum StaggerAxisEnum
    {
        [XmlEnum("y")]
        AxisY,

        [XmlEnum("x")]
        AxisX
    }

    public class Map
    {
        #region fields

        public Dictionary<string, string> _map;

        private List<object> _objects;
        private List<Tile> _tiles;
        private List<object> _tileInstances;
        private int _offsetX;
        private int _offsetY;
        private List<object> _freeBatchSprites;

        private List<TileSet> TileSet;
        private Canvas _canvas;
        private IList<Layer> _layers;

        public string Version = "1.9";
        public string LuaVersion = "5.1";
        public string TiledVersion = "1.9.2";

        public Orientation Orientation;
        public RenderOrderEnum RenderOrder = RenderOrderEnum.RightDown;
        public int Width;
        public int Height;
        public int TileWidth;
        public int TileHeight;
        public int NextLayerID;
        public int NextObiectID;
        public Properties Properties;


        #endregion

        #region props

        private int HexSideLength;
        public StaggerAxisEnum StaggerAxis;
        public staggerindexEnum StaggerIndex;

        #endregion

        ///<summary>Instance a new map</summary>
        ///<param name="path">Path to the map file.</param>
        ///<param name="plugins">A list of plugins to load.</param>
        ///<param name="ox">Offset of map on the X axis.</param>
        ///<param name="oy">Offset of map on the Y axis</param>
        public Map(string path, IEnumerable<string> plugins, int ox, int oy)
        {
            //LoadPlugins(plugins);

            _objects = new List<object>();
            _tiles = new List<Tile>();
            _tileInstances = new List<object>();
            _offsetX = ox;
            _offsetY = oy;
            _freeBatchSprites = new List<object>();

            // Set Tiles, Images

            var gid = 1;
            foreach (var tileSet in TileSet)
            {
                if (tileSet.Image != null)
                {
                    if (Love.Graphics.IsCreated())
                    {
                        var formattedPath = Utils.FormatPath(Path.Combine(path, tileSet.ImagePath));

                        if (STI.Cache.ContainsKey(formattedPath) == false)
                        {
                            Utils.FixTransparentColor(tileSet, formattedPath);
                            Utils.CacheImage(formattedPath, tileSet.Image);
                        }
                        else
                        {
                            tileSet.Image = STI.Cache[formattedPath];
                        }
                    }
                    gid = SetTiles(TileSet.IndexOf(tileSet), tileSet, gid);
                }
                else if (tileSet.TileCount > 0)
                {
                    var files = new List<string>();
                    var ids = new List<int>();
                    for (var j = 0; j < tileSet.TileCount; j++)
                    {
                        files.Add(Utils.FormatPath(Path.Combine(path, tileSet.Tiles[j].TilePath)));
                        ids.Add(tileSet.Tiles[j].ID);
                    }

                    var map = AtlasUtils.Atlas(files, MapSortEnum.ID, ids);

                    if (Love.Graphics.IsCreated())
                    {
                        var formattedPath = Utils.FormatPath(Path.Combine(path, tileSet.Name));

                        if (STI.Cache.ContainsKey(formattedPath) == false)
                        {
                            Utils.CacheImage(formattedPath, map.Image);
                        }
                        else
                        {
                            tileSet.Image = STI.Cache[formattedPath];
                        }
                    }

                    gid = SetAtlasTiles(TileSet.IndexOf(tileSet), tileSet, map.Coords, gid);

                }
            }

            //var layers = new List<Layer>();

        }

        public void GroupAppendToList(IList<Layer> layers, Layer layer)
        {
            if (layer.Type == LayerEnum.ObjectGroup)
            {
                foreach (var groupLayer in layers)
                {
                    groupLayer.Name = $"{layer.Name}.{groupLayer.Name}";
                    groupLayer.Visible = layer.Visible;
                    groupLayer.Opacity = layer.Opacity * groupLayer.Opacity;
                    groupLayer.OffsetX = layer.OffsetX + groupLayer.OffsetX;
                    groupLayer.OffsetY = layer.OffsetY + groupLayer.OffsetY;

                    foreach (var pair in layer.Properties)
                    {
                        if (groupLayer.Properties.ContainsKey(pair.Key))
                            groupLayer.Properties.Add(pair.Key, pair.Value);
                    }
                    GroupAppendToList(layers, groupLayer);
                }
            }
            else
            {
                layers.Add(layer);
            }
        }

        public void SetTileData(Layer layer)
        {
            if (layer.Chunks != null)
            {
                foreach (var chunk in layer.Chunks)
                {
                    SetTileData(chunk);
                }
                return;
            }

            //var map = new 
            for (int i = 0; i < layer.Height; i++)
            {

            }
        }

        public void LoadPlugins(IEnumerable<string> plugins, string cwd = null)
        {
            throw new NotImplementedException("未实现STI的插件导入功能。");
            //foreach (var plugin in plugins)
            //{
            //    var pluginModulePath = $"{cwd}plugins.{plugin}";

            //    //var ok
            //}
        }


        private int SetAtlasTiles(int v, TileSet tileSet, IDictionary<int, string> coords, int gid)
        {
            return 1;
        }

        public int SetTiles(int i, TileSet tileSet, int gid)
        {
            return 1;
        }

        public void Resize(int w = -1, int h = -1)
        {
            if (Love.Graphics.IsCreated())
            {
                if (w <= 0) w = Love.Graphics.GetWidth();
                if (h <= 0) h = Love.Graphics.GetHeight();
                _canvas = Love.Graphics.NewCanvas(w, h);
                _canvas.SetFilter(FilterMode.Nearest, FilterMode.Nearest); ;
            }
        }

        public Tile FilppedGID(int gid)
        {
            var bit31 = 2147483648;
            var bit30 = 1073741824;
            var bit29 = 536870912;
            var filpX = false;
            var filpY = false;
            var filpD = false;
            var realgid = gid;
            if (realgid >= bit31)
            {
                realgid = (int)((uint)realgid - bit31);
                filpX = !filpX;
            }

            if (realgid >= bit30)
            {
                realgid = realgid - bit30;
                filpY = !filpY;
            }

            if (realgid >= bit29)
            {
                realgid = realgid - bit29;
                filpD = !filpD;
            }

            var tile = _tiles[gid];

            var data = new Tile()
            {
                ID = tile.ID,
                GID = (uint)gid,
                TileSet = tile.TileSet,
                Frame = tile.Frame,
                Time = tile.Time,
                WIdth = tile.WIdth,
                Height = tile.Height,
                Offset = tile.Offset,
                Quad = tile.Quad,
                Properties = tile.Properties,
                Terrain = tile.Terrain,
                Animation = tile.Animation,
                SX = tile.SX,
                SY = tile.SY,
                R = tile.R,
            };

            if (filpX)
            {
                if (filpY && filpD)
                {
                    data.R = Mathf.Rad2Deg * (-90);
                    data.SY = -1;
                }
                else if (filpY)
                {
                    data.SX = -1;
                    data.SY = -1;
                }
                else if (filpD)
                {
                    data.R = Mathf.Rad2Deg * (90);
                }
                else
                {
                    data.SX = -1;
                }
            }
            else if (filpY)
            {
                if (filpD)
                {
                    data.R = Mathf.Rad2Deg * (-90);
                }
                else
                {
                    data.SY = -1;
                }
            }
            else if (filpD)
            {
                data.R = Mathf.Rad2Deg * 90;
                data.SY = -1;
            }

            return _tiles[gid];

        }

        public Properties GetLayerProperties(Layer layer)
        {
            return layer.Properties;
        }
        public Properties GetObjectProperties(STIShape shape)
        {
            return shape.Properties;
        }

        public void SetLayerTile(Layer layer, int x, int y, uint gid)
        {
            var tile = layer.Data[y, x];
            Tile instance = null;
            if (tile != null)
            {

                // TODO
            }
        }


        public Vector2 GetLayerTilePosition(Layer layer, Tile tile, int x, int y)
        {
            var tileW = this.TileWidth;
            var tileH = this.TileHeight;
            var tileX = 0f;
            var tileY = 0f;

            if (Orientation == Orientation.Orthogonal)
            {
                tileX = (x - 1) * tileW + tile.Offset.X;
                tileY = (y - 0) * tileH + tile.Offset.Y - tile.Height;
                var vec = Utils.Compensate(tile, tileX, tileY, tileW, tileH);
                tileX = vec.X;
                tileY = vec.Y;
            }
            else if (Orientation == Orientation.Isometric)
            {
                tileX = (x - y) * (tileW / 2) + tile.Offset.X + layer.Width * tileW / 2 - TileWidth / 2;
                tileY = (x + y - 2) * (tileH / 2) + tile.Offset.Y;
            }
            else
            {
                var sideLen = HexSideLength;
                //if(StaggerAxis)
            }

            throw new NotImplementedException();
        }


        public void Draw(float tx = 0, float ty = 0, float sx = 1, float sy = 1)
        {
            var currentCanvas = Love.Graphics.GetCanvas();
            Love.Graphics.SetCanvas(_canvas);
            Love.Graphics.Clear();

            // Scale map to 1.0 to draw onto canvas, this fixes tearing issues
            // Map is translated to correct position so the right section is drawn

            Love.Graphics.Push();
            Love.Graphics.Origin();
            Love.Graphics.Translate(Mathf.Floor(tx), Mathf.Floor(ty));

            foreach (var layer in _layers)
            {
                if (layer.Visible && layer.Opacity > 0)
                {
                    DrawLayer(layer);
                }
            }

            Love.Graphics.Pop();
            Love.Graphics.Push();
            Love.Graphics.Origin();
            Love.Graphics.Scale(sx, sy);

            Love.Graphics.SetCanvas(currentCanvas);
            Love.Graphics.Draw(_canvas);
            Love.Graphics.Pop();
        }

        public void DrawLayer(Layer layer)
        {
            var color = Love.Graphics.GetColor();
            var r = color.r;
            var g = color.g;
            var b = color.b;
            var a = color.a;
            Love.Graphics.SetColor(r, g, b, a * layer.Opacity);
            layer.Draw();
            Love.Graphics.SetColor(r, g, b, a);
        }

        public void DrawTileLayer(Layer layer)
        {
            //if type(layer) == "string" or type(layer) == "number" then
            //    layer = self.layers[layer]
            //end

            if (layer.Chunks != null)
            {
                foreach (var chunk in layer.Chunks)
                {
                    foreach (var batch in chunk.Batches)
                    {
                        Love.Graphics.Draw(batch, 0, 0);
                    }
                }
                return;
            }

            foreach (var batch in layer.Batches)
            {
                Love.Graphics.Draw(batch, Mathf.Floor(layer.X), Mathf.Floor(layer.Y));
            }
        }

        public void DrawObjectLayer(Layer layer)
        {
            //if type(layer) == "string" or type(layer) == "number" then
            //    layer = self.layers[layer]
            //end

            var line = Color.FromRGBA(160, 160, 160, 255 * layer.Opacity);
            var fill = Color.FromRGBA(160, 160, 160, 255 * layer.Opacity / 2);
            var color = Love.Graphics.GetColor();
            var r = color.r;
            var g = color.g;
            var b = color.b;
            var a = color.a;
            var reset = Color.FromRGBA(r, g, b, a * layer.Opacity);

            foreach (var shape in layer.Objects)
            {
                if (shape.Type != STIShapeEnum.Point)
                {
                    DrawShape(shape, shape.Type, line, fill);
                }
                else
                {
                    Love.Graphics.Points(shape.X, shape.Y);
                }
            }

            Love.Graphics.SetColor(reset);

            foreach (var batch in layer.Batches)
            {
                Love.Graphics.Draw(batch, 0, 0);
            }

            Love.Graphics.SetColor(r, g, b, a);
        }


        public void DrawImageLayer(Layer layer)
        {
            Love.Graphics.Draw(layer.Image.Img, layer.X, layer.Y);
        }

        public Vector2[] SortVertices(STIShape obj)
        {
            throw new NotImplementedException();
        }

        public void DrawShape(STIShape obj, STIShapeEnum shapeType, Color line, Color fill)
        {
            var vertex = SortVertices(obj);

            if (shapeType == STIShapeEnum.Polyline)
            {
                Love.Graphics.SetColor(line);
                Love.Graphics.Line(vertex);
                return;
            }
            else if (shapeType == STIShapeEnum.Polygon)
            {
                Love.Graphics.SetColor(fill);
                if (Love.Mathf.IsConvex(vertex) == false)
                {
                    var triangles = Love.Mathf.Triangulate(vertex);
                    foreach (var triangle in triangles)
                    {
                        var vecs = new Vector2[] { triangle.a, triangle.b, triangle.c };
                        Love.Graphics.Polygon(DrawMode.Fill, vecs);
                    }
                }
                else
                {
                    Love.Graphics.Polygon(DrawMode.Fill, vertex);
                }
            }
            else
            {
                Love.Graphics.SetColor(fill);
                Love.Graphics.Polygon(DrawMode.Fill, vertex);
            }

            Love.Graphics.SetColor(fill);
            Love.Graphics.Polygon(DrawMode.Fill, vertex);

        }



    }
}
